package by.bsuir.m4a;

import java.math.BigDecimal;

public class Matrixes {
        
        public static final int NUMBER_OF_DIGITS_AFTER_POINT = 4;
        
        public static final double PRECISION = 0.0001;
        
        public static final double RANGE = 0.0001;
        
        private double[][] matrixD = {{-1.33, 0.21, 0.17, 0.12, -0.13},
                                                  {-0.13, -1.33, 0.11, 0.17, 0.12},
                                                  {0.12, -0.13, -1.33, 0.11, 0.17},
                                                  {0.17, 0.12, -0.13, -1.33, 0.11},
                                                  {0.11, 0.67, 0.12, -0.13, -1.33}};
        
        private double[][] matrixC = {{0.01, 0, -0.02, 0, 0},
                                                  {0.01, 0.01, 0, -0.02, 0},
                                                  {0, 0.01, 0.01, 0, -0.02},
                                                  {0, 0, 0.01, 0.01, 0},
                                                  {0, 0, 0, 0.01, 0.01}};
        
        private double[] vectorB = {1.2, 2.2, 4.0, 0.0, -1.2};
        
        private double[] vectorC;
        
        private double[] vectorX;
        
        private double[][] matrixA;
        
        private double[][] matrixB;
        
        private double[][] matrixE = {{1, 0, 0, 0, 0},
                {0, 1, 0, 0, 0},
                {0, 0, 1, 0, 0},
                {0, 0, 0, 1, 0},
                {0, 0, 0, 0, 1}};
        
        public Matrixes(int variant) {
        	matrixA = new double[matrixC.length][matrixC[0].length];
        	vectorC = new double[vectorB.length];
        	vectorX = new double[vectorB.length];
        	matrixB = new double[matrixA.length][matrixA[0].length];
        	calculateMatrixA(variant);
        }
        
        public Matrixes(double[][] matrixA, double[] vectorB) {
        	this.matrixA = matrixA;
        	this.vectorB = vectorB;
        	vectorC = new double[vectorB.length];
        	vectorX = new double[vectorB.length];
        	matrixB = new double[matrixA.length][matrixA[0].length];
        }
        
        private void calculateMatrixA(int variant) {
                for (int i = 0; i < matrixA.length; i++) {
                        for (int j = 0; j < matrixA[0].length; j++) {
                                matrixA[i][j] = matrixC[i][j] * variant + matrixD[i][j];
                        }
                }
        }
        
        private void calculateMatrixBAndMatrixC() throws NoRootsException {
            for (int i = 0; i < matrixA.length; i++) {
            	if (matrixA[i][i] == 0) {
            		throw new NoRootsException();
            	}
            	for (int j = 0; j < matrixA[0].length; j++) {
            		matrixB[i][j] = matrixE[i][j] - matrixA[i][j] / matrixA[i][i];
            	}
            	vectorC[i] = vectorB[i] / matrixA[i][i];
            }
        }
                
        private void printMatrix(double[][] matrix) {
        	BigDecimal value;
        	
            for (int i = 0; i < matrix.length; i++) {
                for (int j = 0; j < matrix[0].length; j++) {
                	value = BigDecimal.valueOf(matrix[i][j]);
                    value = value.setScale(NUMBER_OF_DIGITS_AFTER_POINT, BigDecimal.ROUND_HALF_UP);
                    System.out.print(" " + value + " ");
                }
                System.out.println();
            }
        }
        
        private void printVector(double[] vector) {
        	BigDecimal value;
        	
        	for (Double x : vector) {
        		value = BigDecimal.valueOf(x);
        		value = value.setScale(NUMBER_OF_DIGITS_AFTER_POINT, BigDecimal.ROUND_HALF_UP);
        		System.out.println(value);
        	}
        }
        
        private void printNorms(double[][] matrix) {
        	System.out.println("1 norm: " + getFirstNorm(matrix));
        	System.out.println("2 norm: " + getSecondNorm(matrix));
        	System.out.println("3 norm: " + getThirdNorm(matrix));
        }
        
        private double getFirstNorm(double[][] matrix) {
        	double sum;
        	double maxSum = 0;
        	
            for (int i = 0; i < matrix.length; i++) {
            	sum = 0;
                for (int j = 0; j < matrix[0].length; j++) {
                	sum += Math.abs(matrix[i][j]);
                }
                maxSum = (sum > maxSum) ? sum : maxSum;
            }
            return maxSum;
        }
        
        private double getSecondNorm(double[][] matrix) {
        	double sum = 0;
        	
            for (int i = 0; i < matrix.length; i++) {
                for (int j = 0; j < matrix[0].length; j++) {
                	sum += matrix[i][j] * matrix[i][j];
                }
            }
            return sum;
        }
        
        private double getThirdNorm(double[][] matrix) {
        	double sum;
        	double maxSum = 0;
        	
            for (int j = 0; j < matrix[0].length; j++) {
            	sum = 0;
                for (int i = 0; i < matrix.length; i++) {
                	sum += Math.abs(matrix[i][j]);
                }
                maxSum = (sum > maxSum) ? sum : maxSum;
            }
            return maxSum;
        }
        
        public double[] calculateRootsByIterations() throws NoRootsException {
        	double[] vectorX1;;
        	int iteration = 0;
        	vectorX = new double[vectorB.length];
        	
        	System.out.println("----------------------Iterations method-----------------------");
        	System.out.println("Matrix A:");
        	printMatrix(matrixA);
        	calculateMatrixBAndMatrixC();
        	System.out.println("Matrix B:");
        	printMatrix(matrixB);
        	printNorms(matrixB);
        	if ((getFirstNorm(matrixB) >= 1) && (getSecondNorm(matrixB) >= 1) && (getThirdNorm(matrixB) >= 1)) {
        		throw new NoRootsException();
        	}
        	do {
        		iteration++;
        		vectorX1 = vectorX;
        		vectorX = new double[vectorB.length];
                for (int i = 0; i < matrixB.length; i++) {
                    for (int j = 0; j < matrixB[0].length; j++) {
                            vectorX[i] += matrixB[i][j] * vectorX1[j];
                    }
                    vectorX[i] += vectorC[i];
                }
        	} while (getMistake(vectorX, vectorX1) >= PRECISION);
        	System.out.println("Vector X:");
        	printVector(vectorX);
        	System.out.println("Iterations: " + iteration);
        	return vectorX.clone();
        }
        
        public double[] calculateRootsByZeidel() throws NoRootsException {
        	double[] vectorX1;;
        	int iteration = 0;
        	vectorX = new double[vectorB.length];
        	
        	System.out.println("----------------------Zeidel method-----------------------");
        	System.out.println("Matrix A:");
        	printMatrix(matrixA);
        	calculateMatrixBAndMatrixC();
        	System.out.println("Matrix B:");
        	printMatrix(matrixB);
        	System.out.println("1 norm: " + getFirstNorm(matrixB));
        	System.out.println("3 norm: " + getThirdNorm(matrixB));
        	if ((getFirstNorm(matrixB) >= 1) && (getThirdNorm(matrixB) >= 1)) {
        		throw new NoRootsException();
        	}
        	do {
        		iteration++;
        		vectorX1 = vectorX;
        		vectorX = new double[vectorB.length];
                for (int i = 0; i < matrixB.length; i++) {
                	for (int z = 0; z < i; z++) {
                		vectorX[i]  += matrixB[i][z] * vectorX[z];
                	}
                    for (int j = i; j < matrixB[0].length; j++) {
                            vectorX[i] += matrixB[i][j] * vectorX1[j];
                    }
                    vectorX[i] += vectorC[i];
                }
        	} while (getMistake(vectorX, vectorX1) >= PRECISION);
        	System.out.println("Vector X:");
        	printVector(vectorX);
        	System.out.println("Iterations: " + iteration);
        	return vectorX.clone();
        }
        
        private double getMistake(double[] vectorX, double[] vectorX1) {
        	double mistake;
        	double maxMistake = 0;
        	
            for (int i = 0; i < vectorX1.length; i++) {
            	mistake = Math.abs(vectorX[i] - vectorX1[i]);
            	maxMistake = (mistake > maxMistake) ? mistake : maxMistake;
            }
            return maxMistake;
        }
        
      
        
        public void checkRoots(double[] vector) {
    		double[] vectorB1 = new double[vector.length];;
    	
            for (int i = 0; i < vector.length; i++) {
                    for (int j = 0; j < vector.length; j++) {
                            vectorB1[i] += matrixA[i][j] * vector[j];
                    }
                    
            }
        	System.out.println("Checking roots:");
        	System.out.println("Vector B:");
        	printVector(vectorB);
        	System.out.println("Vector B1:");
        	printVector(vectorB1);
        }
                        
        public static void main(String[] args) {
            Matrixes matrixes = new Matrixes(3);
            
            try {
				matrixes.checkRoots(matrixes.calculateRootsByIterations());
				matrixes.checkRoots(matrixes.calculateRootsByZeidel());
			} catch (NoRootsException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
        }

}

